11 设计模式——观察者模式

返回设计模式博客目录

介绍


观察者(Observer)模式:定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布—订阅模式、模型—视图模式,它是对象行为型模式。

观察者模式是一个使用率非常高的模式,它最常用的地方是 GUI 系统、订阅——发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。

优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 目标与观察者之间建立了一套触发机制。

缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

使用场景

  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的信息交换场景,如消息队列、事件总线的处理机制。

使用例子

  • 常见的发布—订阅模式。
  • ListView 的 Adapter 的 notifyDataSetChanged 更新方法。
  • BroadcastReceiver。
  • 开源库 EventBus。
  • RxJava。

结构与实现


观察者模式包含以下主要角色。

  • 抽象主题(Subject):也就是被观察(Observable)的角色。它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(ConcreteSubject):也就是具体被观察者(ConcreteObservable)。它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer):它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(ConcreteObserver):实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

其结构图如下图所示。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class ObserverPattern {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver1();
Observer observer2 = new ConcreteObserver2();
subject.add(observer1);
subject.add(observer2);
subject.notifyObserver();
}
}
// 抽象目标,被观察者
abstract class Subject {
// 观察者集合
protected List<Observer> observers = new ArrayList<Observer>();
// 增加观察者方法
public void add(Observer observer) {
observers.add(observer);
}
// 删除观察者方法
public void remove(Observer observer) {
observers.remove(observer);
}
public abstract void notifyObserver(); // 通知观察者方法
}
// 具体目标,具体被观察者
class ConcreteSubject extends Subject {
public void notifyObserver() {
System.out.println("具体目标发生改变...");
System.out.println("--------------");
for(Object obs:observers) {
// 通知所有观察者
((Observer)obs).response();
}
}
}
// 抽象观察者
interface Observer {
void response(); // 反应
}
// 具体观察者 1
class ConcreteObserver1 implements Observer {
public void response() {
System.out.println("具体观察者1作出反应!");
}
}
// 具体观察者1
class ConcreteObserver2 implements Observer {
public void response() {
System.out.println("具体观察者2作出反应!");
}
}

我们也可以利用 JDK 中 Observable 类和 Observer 接口实现。观察者实现 Observer 接口,被观察者继承 Observable 类。被观察者通过 Observable 类的 addObserver 方法添加观察者。其代码形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 具体观察者
public class MyObserver implements Observer {
private String mName;
public MyObserver(String name) {
mName = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(mName + "-->" + "update: " + arg);
}
}
// 具体被观察者
public class MyObservable extends Observable {
public void sendChangeMsg(String content) {
// 方法继承自 Observable,标示状态或是内容发生改变
setChanged();
// 方法继承自 Observable,通知所有观察者,最后会调用每个 Observer 的 update 方法
notifyObservers(content);
}
}
// 测试
public class ObserverPatternTest {
@Test
public void test1() throws Exception {
MyObservable myObservable = new MyObservable();
MyObserver myObserver1 = new MyObserver("observer-1");
MyObserver myObserver2 = new MyObserver("observer-2");
myObservable.addObserver(myObserver1);
myObservable.addObserver(myObserver2);
// 发布消息
myObservable.sendChangeMsg("发布更新啦");
}
}

被观察者通过 setChanged() 方法标示改变,通过 notifyObservers 方法通知所有观察者。notifyObservers 方法会遍历所有的观察者 Observer,并调用它们的 update 方法。notifyObservers 方法中的参数就是最后传到观察者 update 方法的参数 Object arg。

示例


利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。

分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。

这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBRate)类是具体目标, 它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。下图所示是其结构图。

程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class RMBRateTest {
public static void main(String[] args) {
Rate rate = new RMBRate();
Company watcher1 = new ImportCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
// 抽象目标:汇率
abstract class Rate {
protected List<Company> companies = new ArrayList<Company>();
// 增加观察者方法
public void add(Company company) {
companies.add(company);
}
// 删除观察者方法
public void remove(Company company) {
companies.remove(company);
}
public abstract void change(int number);
}
// 具体目标:人民币汇率
class RMBRate extends Rate {
public void change(int number) {
for(Company observer : companies) {
((Company) observer).response(number);
}
}
}
// 抽象观察者:公司
interface Company {
void response(int number);
}
// 具体观察者1:进口公司
class ImportCompany implements Company {
public void response(int number) {
if (number>0) {
System.out.println("人民币汇率升值"+number+"个基点,降低了进口产品成本,提升了进口公司利润率。");
} else if (number<0) {
System.out.println("人民币汇率贬值"+(-number)+"个基点,提升了进口产品成本,降低了进口公司利润率。");
}
}
}
// 具体观察者2:出口公司
class ExportCompany implements Company {
public void response(int number) {
if (number>0) {
System.out.println("人民币汇率升值"+number+"个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
} else if(number<0) {
System.out.println("人民币汇率贬值"+(-number)+"个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
}
}
}

程序运行结果如下:

1
2
3
4
人民币汇率升值10个基点,降低了进口产品成本,提升了进口公司利润率。
人民币汇率升值10个基点,降低了出口产品收入,降低了出口公司的销售利润率。
人民币汇率贬值9个基点,提升了进口产品成本,降低了进口公司利润率。
人民币汇率贬值9个基点,提升了出口产品收入,提升了出口公司的销售利润率。

ANDROID 源码中的实现


ListView 是 ANDROID 中最重要的控件之一,而 ListView 最重要的一个功能就是 Adapter。当我们往 ListView 添加数据后,都会调用 Adapter 的 notifyDataSetChanged() 方法,通知界面刷新。这是一个典型的观察者模式案例。我们追踪 notifyDataSetChanged() 这个方法,它定义在 BaseAdapter 中,具体代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
// 数据集(被观察者)
private final DataSetObservable mDataSetObservable = new DataSetObservable();
// 代码省略
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* 当数据集变化时,通知所有观察者
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
}

追踪 mDataSetObservable.notifyChanged() 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 数据集被观察者
*/
public class DataSetObservable extends Observable<DataSetObserver> {
// 调用每个观察者的 onChange 函数来通知它们被观察者发生了变化
public void notifyChanged() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
// 代码省略
}

而这些观察者是 ListView 通过 setAdapter 方法产生的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public void setAdapter(ListAdapter adapter) {
// 如果已经有了一个 Adapter,那么先注销该 Adapter 对应的观察者
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
// 代码省略
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
// 获取数据的数量
mItemCount = mAdapter.getCount();
checkFocus();
// 注意这里:创建一个数据集观察者
mDataSetObserver = new AdapterDataSetObserver();
// 将这个观察者注册到 Adapter 中,实际上是注册到 DataSetObservable 中
mAdapter.registerDataSetObserver(mDataSetObserver);
// 代码省略
} else {
// 代码省略
}
requestLayout();
}

观察者 AdapterDataSetObserver 定义在 ListView 的父类 AbsListView 中,具体代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
// 代码省略
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
}

通过 super.onChanged() 继续跟踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class AdapterView<T extends Adapter> extends ViewGroup {
// 代码省略
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
// 重新布局 ListView、GridView 等 AdapterView 组件
requestLayout();
}
// 代码省略
public void clearSavedState() {
mInstanceState = null;
}
}
}

到这里我们就知道了,当 ListView 的数据发生变化时,调用 Adapter 的 notifyDataSetChanged 函数,这个函数又会调用 DataSetObservable 的 notifyChanged 函数,这个函数会调用所有观察者(AdapterDataSetObserver)的 onChanged 方法,在 onChanged 函数中又会调用 ListView 重新布局的函数使得 ListView 刷新界面。这就是一个观察者模式!

实战


利用观察者模式设计一个学校铃声的事件处理程序。

分析:在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现,下图给出了学校铃声的事件模型。

现在用“观察者模式”来实现该事件处理模型。首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声);再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法;然后,定义一声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e);最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。下图给出了学校铃声事件处理程序的结构。

程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class BellEventTest {
public static void main(String[] args) {
BellEventSource bell = new BellEventSource(); // 铃(事件源)
bell.addPersonListener(new TeachEventListener()); // 注册监听器(老师)
bell.addPersonListener(new StudentEventListener()); // 注册监听器(学生)
bell.ring(true); // 打上课铃声
System.out.println("------------");
bell.ring(false); // 打下课铃声
}
}
// 铃声事件类:用于封装事件源及一些与事件相关的参数
class RingEvent extends EventObject {
private static final long serialVersionUID = 1L;
private boolean sound; // true 表示上课铃声,false 表示下课铃声
public RingEvent(Object source,boolean sound) {
super(source);
this.sound=sound;
}
public void setSound(boolean sound) {
this.sound=sound;
}
public boolean getSound() {
return this.sound;
}
}
// 目标类:事件源,铃
class BellEventSource {
private List<BellEventListener> listener; // 监听器容器
public BellEventSource() {
listener = new ArrayList<BellEventListener>();
}
// 给事件源绑定监听器
public void addPersonListener(BellEventListener ren) {
listener.add(ren);
}
// 事件触发器:敲钟,当铃声 sound 的值发生变化时,触发事件。
public void ring(boolean sound) {
String type = sound? "上课铃" : "下课铃";
System.out.println(type+"响!");
RingEvent event = new RingEvent(this, sound);
notifies(event); // 通知注册在该事件源上的所有监听器
}
// 当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法)
protected void notifies(RingEvent e) {
BellEventListener person;
Iterator<BellEventListener> iterator = listener.iterator();
while(iterator.hasNext()) {
person = iterator.next();
person.heardBell(e);
}
}
}
// 抽象观察者类:铃声事件监听器
interface BellEventListener extends EventListener {
// 事件处理方法,听到铃声
void heardBell(RingEvent e);
}
//具体观察者类:老师事件监听器
class TeachEventListener implements BellEventListener {
public void heardBell(RingEvent e) {
if (e.getSound()) {
System.out.println("老师上课了...");
} else {
System.out.println("老师下课了...");
}
}
}
// 具体观察者类:学生事件监听器
class StudentEventListener implements BellEventListener {
public void heardBell(RingEvent e) {
if (e.getSound()) {
System.out.println("同学们,上课了...");
} else {
System.out.println("同学们,下课了...");
}
}
}

程序运行结果如下:

1
2
3
4
5
6
7
上课铃响!
老师上课了...
同学们,上课了...
------------
下课铃响!
老师下课了...
同学们,下课了...